/**
 * Copyright (c) 2020 Coder League
 * All rights reserved.
 *
 * File：SystemLogAspect.java
 * History:
 *         2020年12月18日: Initially created, Chrise.
 */
package club.coderleague.cfss.aspects;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ValidationException;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import club.coderleague.cfss.annotations.Request;
import club.coderleague.cfss.domain.beans.OperateResult;
import club.coderleague.cfss.domain.beans.UserSession;
import club.coderleague.cfss.domain.entities.Systemlogs;
import club.coderleague.cfss.domain.enums.EntityState;
import club.coderleague.cfss.domain.enums.ResultCode;
import club.coderleague.cfss.exceptions.RequestProcessException;
import club.coderleague.cfss.service.systemlog.SystemLogService;
import club.coderleague.cfss.util.HttpServletUtil;

/**
 * 系统日志切面。
 * @author Chrise
 */
@Aspect
@Component
public class SystemLogAspect {
	private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
	
	@Autowired
	private SystemLogService slService;
	
	/**
	 * 定义切入点。
	 * @author Chrise 2020年12月18日
	 */
	@Pointcut("@within(org.springframework.stereotype.Controller)")
	public void pointCut() {}
	
	/**
	 * 执行切入点调用。
	 * @author Chrise 2020年12月18日
	 * @param pjp 处理中的切入点。
	 * @return 切入点调用结果。
	 */
	@Around("pointCut()")
	public Object execute(ProceedingJoinPoint pjp) {
		boolean noException = true;
		Object response = null, exception = null;
		
		try {
			response = pjp.proceed();
		} catch (ValidationException e) {
			noException = false;
			exception = new OperateResult(false, ResultCode.WARN.getValue(), e.getMessage());
	    	logger.error(e.getMessage(), e);
		} catch (RequestProcessException e) {
			noException = false;
	        exception = new OperateResult(false, e.getResultcode().getValue(), e.getMessage());
	    	logger.error(e.getMessage(), e);
		} catch (Throwable t) {
			noException = false;
			exception = "系统错误！";
			logger.error(t.getMessage(), t);
		}
		
		if (!noException) {
			MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
			Method method = methodSignature.getMethod();
			
			if (method.isAnnotationPresent(ResponseBody.class)) {
				response = new ModelMap().addAttribute("status", noException).addAttribute("result", exception);
			} else response = "/500";
		}
		
		try {
			Systemlogs log = this.packageLog(pjp);
			if (log != null) {
				log.setReturnresult(noException);
				log.setResponsedata(response != null ? new ObjectMapper().writeValueAsString(response) : null);
				this.slService.save(log);
			}
		} catch (Throwable t) {
			logger.error("保存系统日志发生错误！->" + t.getMessage(), t);
		}
		
		return response;
	}
	
	/**
	 * 封装日志。
	 * @author Chrise 2020年12月19日
	 * @param pjp 处理中的切入点。
	 * @return 日志对象。
	 * @throws JsonProcessingException JSON处理错误。
	 */
	private Systemlogs packageLog(ProceedingJoinPoint pjp) throws JsonProcessingException {
		Signature signature = pjp.getSignature();
		MethodSignature methodSignature = (MethodSignature)signature;
		Method method = methodSignature.getMethod();
		if (!AnnotatedElementUtils.isAnnotated(method, Request.class)) return null;
		
		ObjectMapper mapper = new ObjectMapper();
		
		UserSession us = HttpServletUtil.getUserSession();
		Long operator = us != null ? us.getUserid() : null;
		Integer usergroup = us != null ? us.getUsergroup() : null;
		
		Class<?>[] parametertypes = methodSignature.getParameterTypes();
		List<String> parametertypeList = new ArrayList<>();
		for (Class<?> parametertype : parametertypes) {
			parametertypeList.add(parametertype.getTypeName());
		}
		
		StringBuilder invokemethodBuilder = new StringBuilder(pjp.getSignature().getDeclaringTypeName()).append("#")
			.append(pjp.getSignature().getName()).append("(").append(StringUtils.join(parametertypeList, ", ")).append(")");
		
		HttpServletRequest hsr = HttpServletUtil.getHttpServletRequest();
		Map<String, Object> requestdata = new HashMap<>();
		Enumeration<?> parameterNames = hsr.getParameterNames();
		while (parameterNames.hasMoreElements()) {
			String parameterName = (String) parameterNames.nextElement();
			requestdata.put(parameterName, hsr.getParameterValues(parameterName).length > 1 ? mapper.writeValueAsString(hsr.getParameterValues(parameterName)) : hsr.getParameter(parameterName));
		}
		
		Request request = AnnotatedElementUtils.findMergedAnnotation(method, Request.class);
		
		Systemlogs log = new Systemlogs(EntityState.VALID.getValue(), operator, usergroup, new Date(), HttpServletUtil.getRemoteHost(), request.value(), 
				request.type().getValue(), invokemethodBuilder.toString(), null, mapper.writeValueAsString(requestdata), null);
		return log;
	}
}
